package avicit.bdp.dds.service.quartz;

import avicit.bdp.common.utils.SpringApplicationContext;
import avicit.bdp.dds.dispatch.enums.CommandType;
import avicit.bdp.dds.dispatch.enums.FailureStrategy;
import avicit.bdp.dds.dispatch.enums.Priority;
import avicit.bdp.dds.dispatch.enums.TaskDependType;
import avicit.bdp.dds.dispatch.enums.TaskType;
import avicit.bdp.dds.dispatch.model.ProcessData;
import avicit.bdp.dds.dispatch.model.TaskNode;
import avicit.bdp.dds.dispatch.task.monitordir.MonitorDirectoryParameters;
import avicit.bdp.core.util.json.JSONUtils;
import avicit.bdp.dds.api.service.DdsResourceService;
import avicit.bdp.dds.api.service.MonitorDirectoryTaskService;
import avicit.bdp.dds.common.Constants;
import avicit.bdp.dds.common.utils.TaskParametersUtils;
import avicit.bdp.dds.dao.entity.Command;
import avicit.bdp.dds.dao.entity.MonitorDirectoryTask;
import avicit.bdp.dds.dao.entity.ProcessDefinition;
import avicit.bdp.dds.dao.entity.Resource;
import avicit.bdp.dds.dao.entity.Schedule;
import avicit.bdp.dds.service.process.ProcessService;
import avicit.platform6.commons.utils.ComUtil;
import avicit.platform6.core.context.ThreadContextHelper;
import org.apache.commons.lang3.StringUtils;
import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;


/**
 * @author DIGITAL-MAYANJ
 * monitor directory job
 */
public class MonitorDirectoryScheduleJob implements Job {

    /**
     * logger of ProcessScheduleJob
     */
    private static final Logger logger = LoggerFactory.getLogger(MonitorDirectoryScheduleJob.class);

    public ProcessService getProcessService() {
        return SpringApplicationContext.getBean(ProcessService.class);
    }

    public MonitorDirectoryTaskService getMonitorDirectoryService() {
        return SpringApplicationContext.getBean(MonitorDirectoryTaskService.class);
    }

    public DdsResourceService getResourceService() {
        return SpringApplicationContext.getBean(DdsResourceService.class);
    }

    /**
     * Called by the Scheduler when a Trigger fires that is associated with the Job
     *
     * @param context JobExecutionContext
     * @throws JobExecutionException if there is an exception while executing the job.
     */
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        Assert.notNull(getProcessService(), "please call init() method first");

        JobDataMap dataMap = context.getJobDetail().getJobDataMap();
        String taskType = dataMap.getString(Constants.TASK_TYPE);

//        if (!TaskType.MONITORDIRECTORY.name().equals(taskType)) {
//            return;
//        }

        String projectId = dataMap.getString(Constants.PROJECT_ID);
        String processDefinitionId = dataMap.getString(Constants.PROCESS_DEFINITION_ID);
        String monitorDirectory = dataMap.getString(Constants.MONITOR_DIRECTORY);
        String scheduleId = dataMap.getString(Constants.SCHEDULE_ID);
        Date scheduledFireTime = context.getScheduledFireTime();
        Date fireTime = context.getFireTime();

//        logger.info("scheduled fire time :{}, fire time :{}, processDefinitionId id :{}, monitorDirectory: {}",
//                scheduledFireTime, fireTime, processDefinitionId, monitorDirectory);

        ProcessDefinition processDefinition = getProcessService().findProcessDefineById(processDefinitionId);
        if (processDefinition == null) {
            logger.warn("process definition does not exist in db，need not to create command, projectId:{}, processDefinitionId:{}", projectId, processDefinitionId);
            return;
        }

        // release state : online/offline
//        ReleaseState releaseState = ReleaseState.of(processDefinition.getReleaseState());
//        if (releaseState == ReleaseState.OFFLINE) {
//            logger.warn("process definition does not offline，need not to create command, projectId:{}, processDefinitionId:{}", projectId, processDefinitionId);
//            return;
//        }

        Schedule schedule = getProcessService().querySchedule(scheduleId);

        //启动流程
        startProcessDefinition(processDefinition, scheduledFireTime, fireTime, schedule);
    }


    /**
     * 检查包含目录监控节点的流程是否有目录变动，如果有则执行该流程
     *
     * @param processDefinition 流程定义
     * @return boolean result
     */
    private void startProcessDefinition(ProcessDefinition processDefinition, Date scheduledFireTime, Date fireTime, Schedule schedule) {
        // 查询上次同步时间
        MonitorDirectoryTask query = new MonitorDirectoryTask();
        query.setProcessDefinitionId(processDefinition.getId());
        MonitorDirectoryTask monitorDirectoryTask = getMonitorDirectoryService().selectOne(query);
        Date lastUpdateTime = null;
        if (monitorDirectoryTask != null) {
            lastUpdateTime = monitorDirectoryTask.getLastDate();
        }

        ProcessData processData = JSONUtils.parseObject(processDefinition.getProcessDefinitionJson(), ProcessData.class);
        if (processData != null) {
            List<String> resourceIdList = new LinkedList<>();
            List<TaskNode> taskNodeList = (processData.getTasks() == null) ? new ArrayList<>() : processData.getTasks();
            for (TaskNode taskNode : taskNodeList) {
                if (TaskType.MONITORDIRECTORY.name().equalsIgnoreCase(taskNode.getType())) {
                    MonitorDirectoryParameters params = (MonitorDirectoryParameters) TaskParametersUtils.getParameters(taskNode.getType(), taskNode.getParams());
                    if (params != null) {
                        resourceIdList.addAll(params.getDirectories());
                    }
                }
            }
            List<String> directoryList = new ArrayList<>();
            if (!resourceIdList.isEmpty()) {
                directoryList = getResourceService().selectFullNameByIdList(resourceIdList);
            }

            //如果lastUpdateTime为null，则表示第一次运行，查询所有文件
            List<Resource> list = getResourceService().getFileListByLatestModify(lastUpdateTime, directoryList);
            for (Resource resource : list) {
                // TODO 可能存在问题，文件修改后，会造成重复运行，需要检查是否存在流程id，如果存在，则执行流程的重复运行, 流程ID在监控节点执行时返写到该资源ID的记录上
                createCommand(processDefinition.getId(), resource.getId(), resource.getFullName(), scheduledFireTime, fireTime, schedule);
                if (monitorDirectoryTask == null) {
                    // 如果没有查询到扫描记录，插入新的同步记录, 更新时间为文件记录的最后更新时间
                    createScheduleLog(processDefinition.getId(), resource.getLastUpdateDate());
                    lastUpdateTime = resource.getLastUpdateDate();
                } else {
                    updateScheduleLog(monitorDirectoryTask.getId(), resource.getLastUpdateDate());
                }

            }
        }

    }

    private void createCommand(String processDefinitionId, String resourceId, String filePaths, Date scheduledFireTime, Date fireTime, Schedule schedule) {
        Command command = new Command();
        command.setCommandType(CommandType.SCHEDULER.getCode());
        command.setProcessDefinitionId(processDefinitionId);
        command.setScheduleTime(scheduledFireTime);
        command.setStartTime(fireTime);
        if (schedule != null) {
            command.setExecutorId(schedule.getCreatedBy());
            command.setFailureStrategy(schedule.getFailureStrategy());
            command.setWarningType(schedule.getWarningType());
            command.setProcessInstancePriority(schedule.getProcessInstancePriority());
            String workerGroup = StringUtils.isEmpty(schedule.getWorkerGroup()) ? Constants.DEFAULT_WORKER_GROUP : schedule.getWorkerGroup();
            command.setWorkerGroup(workerGroup);
        } else {
            command.setExecutorId(ThreadContextHelper.getUserId());
            command.setFailureStrategy(FailureStrategy.END.getCode());
            command.setWorkerGroup(Constants.DEFAULT_WORKER_GROUP);
            command.setProcessInstancePriority(Priority.MEDIUM.getCode());
        }

        Map<String, String> cmdParam = new HashMap<>();
        cmdParam.put(Constants.CMDPARAM_RESOURCE_ID, resourceId);
        cmdParam.put(Constants.CMDPARAM_FILE_PATHS, filePaths);
        command.setCommandParam(JSONUtils.toJsonString(cmdParam));
        command.setTaskDependType(TaskDependType.TASK_POST.getCode());

        getProcessService().createCommand(command);
    }

    private void createScheduleLog(String processDefinitionId, Date updateTime) {
        MonitorDirectoryTask task = new MonitorDirectoryTask();
        task.setId(ComUtil.getId());
        task.setProcessDefinitionId(processDefinitionId);
        task.setLastDate(updateTime);
        try {
            getMonitorDirectoryService().insert(task);
        } catch (Exception e) {
            logger.error("monitor directory schedule task create log error", e);
        }
    }

    private void updateScheduleLog(String id, Date updateTime) {
        MonitorDirectoryTask task = new MonitorDirectoryTask();
        task.setId(id);
        task.setLastDate(updateTime);
        try {
            getMonitorDirectoryService().updateByPrimaryKeySelective(task);
        } catch (Exception e) {
            logger.error("monitor directory schedule task update log error", e);
        }
    }

}
